﻿global using Devonline.AspNetCore;
global using Devonline.Core;
global using Devonline.Entity;
global using Devonline.Identity.Admin.Models;
global using static Devonline.Core.AppSettings;
using System.Security.Cryptography.X509Certificates;
using Devonline.AspNetCore.OData;
using Devonline.CloudService.Aliyun;
using Devonline.Communication.Messages;
using Devonline.Identity;
using Devonline.Identity.Admin;
using IdentityServer4.EntityFramework.Storage;
using Microsoft.AspNetCore.DataProtection;
#if !DEBUG
using Microsoft.AspNetCore.HttpOverrides;
#endif
using Microsoft.AspNetCore.OData;
using Microsoft.AspNetCore.OData.NewtonsoftJson;
using Microsoft.EntityFrameworkCore;
using Microsoft.OData.ModelBuilder;
using OfficeOpenXml;
using Serilog;

ExcelPackage.LicenseContext = LicenseContext.NonCommercial;

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, logger) => logger.ReadFrom.Configuration(context.Configuration));
var appSetting = builder.AddSetting<AdminSetting>();

// Add services to the container.
builder.AddCommunicator();
var services = builder.Services;
services.AddLogging();
var mvcBuilder = services.AddControllersWithViews(options =>
{
    options.Filters.Add<AuthorizationFilter>();
    options.Filters.Add<ExceptionFilter>();
})
    .AddOData(options => options.Count().Filter().Expand().Select().OrderBy().SetMaxTop(100)
    .AddRouteComponents(DEFAULT_ODATA_ROUTE_PREFIX, new ODataConventionModelBuilder().GetEdmModels<IdentityDbContext>().BuildEdmModel()))
    .AddODataNewtonsoftJson()
    .AddJsonOptions();

#if DEBUG
mvcBuilder.AddRazorRuntimeCompilation();
#endif

if (appSetting.EnableCache)
{
    services.AddStackExchangeRedisCache(options => options.Configuration = appSetting.CacheConfiguration);
}
else
{
    services.AddDistributedMemoryCache(options => options.ExpirationScanFrequency = TimeSpan.FromHours(appSetting.CacheExpireTime));
}

//TODO TBD 数据保护功能待验证
if (appSetting.DataProtection is not null)
{
    var dataProtection = appSetting.DataProtection;
    services.AddSingleton(dataProtection);
    services.AddDataProtection(x => x.ApplicationDiscriminator = dataProtection.ApplicationDiscriminator)
        .SetApplicationName(dataProtection.ApplicationName)
        .PersistKeysToStackExchangeRedis(StackExchange.Redis.ConnectionMultiplexer.Connect(dataProtection.Redis), dataProtection.KeyName);
}

services.AddResponseCompression();
services.AddAuthorization();
services.AddDataService();
services.AddExcelService();
services.AddSingleton<ISmsEndpoint>(appSetting.Sms);
services.AddSingleton<ISmsService<SmsModel>, SmsService>();
services.AddDefaultIdentity<IdentityDbContext>();
services.AddTransient<AuthorizationService>();
services.AddTransient<IDataService<IdentityDbContext, UserClaim, int>, DataService<IdentityDbContext, UserClaim, int>>();

var identityServerBuilder = services.AddIdentityServer(options =>
{
    options.UserInteraction.LoginUrl = appSetting.UserInteraction.Login;
    options.UserInteraction.LogoutUrl = appSetting.UserInteraction.Logout;
    options.UserInteraction.ErrorUrl = appSetting.UserInteraction.Error;
})
.AddAspNetIdentity<User>();

var assemblyName = typeof(Program).Assembly.FullName;
if (appSetting.DatabaseType == DatabaseType.PostgreSQL)
{
    #region pgsql 方案
    ////pgsql 的时间格式, 在 efcore 6.0 中 DateTime 类型的时区不在使用 UTC 的解决办法
    //AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    //AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);

    services.AddDbContext<IdentityDbContext>(options => options.UseNpgsql(appSetting.IdentityDbContext, sql => sql.MigrationsAssembly(assemblyName)));
    services.AddConfigurationDbContext<ConfigurationDbContext>(options => options.ConfigureDbContext = db => db.UseNpgsql(appSetting.IdentityDbContext, sql => sql.MigrationsAssembly(assemblyName)));
    services.AddOperationalDbContext(options => options.ConfigureDbContext = db => db.UseNpgsql(appSetting.IdentityDbContext, sql => sql.MigrationsAssembly(assemblyName)));
    identityServerBuilder.AddConfigurationStore<ConfigurationDbContext>(options => options.ConfigureDbContext = db => db.UseNpgsql(appSetting.IdentityDbContext, sql => sql.MigrationsAssembly(assemblyName)))
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = db => db.UseNpgsql(appSetting.IdentityDbContext, sql => sql.MigrationsAssembly(assemblyName));
        options.EnableTokenCleanup = true;
    });
    #endregion
}
else if (appSetting.DatabaseType == DatabaseType.MySQL)
{
    #region mysql 方案
    var version = new MySqlServerVersion(new Version(5, 7));
    services.AddDbContext<IdentityDbContext>(options => options.UseMySql(appSetting.IdentityDbContext, version, sql => sql.MigrationsAssembly(assemblyName)));
    services.AddConfigurationDbContext<ConfigurationDbContext>(options => options.ConfigureDbContext = db => db.UseMySql(appSetting.IdentityDbContext, version, sql => sql.MigrationsAssembly(assemblyName)));
    services.AddOperationalDbContext(options => options.ConfigureDbContext = db => db.UseMySql(appSetting.IdentityDbContext, version, sql => sql.MigrationsAssembly(assemblyName)));
    identityServerBuilder.AddConfigurationStore<ConfigurationDbContext>(options => options.ConfigureDbContext = db => db.UseMySql(appSetting.IdentityDbContext, version, sql => sql.MigrationsAssembly(assemblyName)))
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = db => db.UseMySql(appSetting.IdentityDbContext, version, sql => sql.MigrationsAssembly(assemblyName));
        options.EnableTokenCleanup = true;
    });
    #endregion
}

//开发环境使用开发者证书
if (builder.Environment.IsDevelopment())
{
    identityServerBuilder.AddDeveloperSigningCredential();
}
else
{
    ArgumentNullException.ThrowIfNull(appSetting.Certificate);
    identityServerBuilder.AddSigningCredential(new X509Certificate2(appSetting.Certificate.Path, appSetting.Certificate.Password));
}

#if !DEBUG
//启用跨域策略
if (!string.IsNullOrWhiteSpace(appSetting.CorsOrigins))
{
    services.AddCors(options => options.AddPolicy(DEFAULT_CORS_POLICY, policy => policy.WithOrigins(appSetting.CorsOrigins.Split(CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))));
}

if (appSetting.ProtocolType == ProtocolType.Https && appSetting.HttpFordwarded)
{
    static void SetSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None && httpContext.Request.Scheme != "https")
        {
            options.SameSite = SameSiteMode.Unspecified;
        }
    }

    services.AddCookiePolicy(options =>
    {
        options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
        options.OnAppendCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions);
        options.OnDeleteCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions);
    });
}
#endif

var auth = services.AddAuthentication();
if (appSetting.Weixin.Enable)
{
    auth.AddWeixin(options => appSetting.Weixin.Configure(options));
}

if (appSetting.GitHub.Enable)
{
    auth.AddGitHub(options => appSetting.GitHub.Configure(options));
}

if (appSetting.Gitee.Enable)
{
    auth.AddGitee(options => appSetting.Gitee.Configure(options));
}

//启用跨域策略
if (!string.IsNullOrWhiteSpace(appSetting.CorsOrigins))
{
    services.AddCors(options => options.AddPolicy(DEFAULT_CORS_POLICY, policy => policy.WithOrigins(appSetting.CorsOrigins.Split(CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))));
}

// TODO 使用 AuthorizationFilter 的过滤器机制的话, 就不需要使用基于策略的授权机制了, 两者二选一
//var provider = services.BuildServiceProvider();
//var authorizationService = provider.GetService<AuthorizationService>();
//services.AddTransient<IAuthorizationHandler, AuthorizationRequirement>();
//services.AddTransient<IAuthorizationRequirement, AuthorizationRequirement>();
////services.AddAuthorization(options => options.AddPolicy(DEFAULT_AUTHORIZATION_POLICY, policy => { policy.AddRequirements(new AuthorizationRequirement(authorizationService)); }));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();

#if !DEBUG
//启用跨域策略
if (!string.IsNullOrWhiteSpace(appSetting.CorsOrigins))
{
    app.UseCors(DEFAULT_CORS_POLICY);
}

if (appSetting.HttpFordwarded)
{
    //使用 webserver header 配置进行 http 重定向
    var fordwardedHeaderOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto };
    fordwardedHeaderOptions.KnownNetworks.Clear();
    fordwardedHeaderOptions.KnownProxies.Clear();
    app.UseForwardedHeaders(fordwardedHeaderOptions);
}
#endif

app.UseException();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseResponseCompression();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers().RequireAuthorization();
    endpoints.MapDefaultControllerRoute().RequireAuthorization();
    endpoints.MapControllerRoute(name: "api", pattern: "api/{controller=Home}/{action=Index}/{id?}");
});

await app.Services.GetRequiredService<IMessageCommunicator>().StartAsync();
app.Run();